#include "tty.h"
#include "io.h"
#include "string.h"

#define CRT_ADDR_REG 0x3D4
#define CRT_DATA_REG 0x3D5

#define CRT_START_ADDR_H 0xC
#define CRT_START_ADDR_L 0xD

#define CRT_CURSOR_H 0xE
#define CRT_CURSOR_L 0xF

#define MEM_BASE 0xB8000
#define MEM_SIZE 0x4000
#define MEM_END (MEM_BASE + MEM_SIZE)
#define WIDTH 80
#define HEIGHT 25
#define ROW_SIZE (WIDTH * 2)
#define SCR_SIZE (ROW_SIZE * HEIGHT)

#define ASCII_NUL 0x00
#define ASCII_ENQ 0x05
#define ASCII_BEL 0x07
#define ASCII_BS 0x08
#define ASCII_HT 0x09
#define ASCII_LF 0x0A
#define ASCII_VT 0x0B
#define ASCII_FF 0x0C
#define ASCII_CR 0x0D
#define ASCII_DEL 0x7F

static uint screen;
static uint pos;

static void set_screen()
{
    out_byte(CRT_ADDR_REG, CRT_START_ADDR_H);
    out_byte(CRT_DATA_REG, (screen >> 9) & 0xff);
    out_byte(CRT_ADDR_REG, CRT_START_ADDR_L);
    out_byte(CRT_DATA_REG, (screen >> 1) & 0xff);
}

static void set_cursor()
{
    out_byte(CRT_ADDR_REG, CRT_CURSOR_H);
    out_byte(CRT_DATA_REG, (pos >> 9) & 0xff);
    out_byte(CRT_ADDR_REG, CRT_CURSOR_L);
    out_byte(CRT_DATA_REG, (pos >> 1) & 0xff);
}

void console_clear()
{
    screen = 0;
    pos = 0;
    set_cursor();
    set_screen();

    u16 *ptr = (u16*) MEM_BASE;
    while (ptr < (u16*) MEM_END)
    {
        *ptr++ = 0x0720;
    }
}

static void scroll_up()
{
    if (screen + SCR_SIZE + ROW_SIZE < MEM_SIZE)
    {
        u32* ptr = (u32*) (MEM_BASE + screen + SCR_SIZE);
        for (size_t i = 0; i < WIDTH * 2; i++)
        {
            *ptr++ = 0x0720;
        }
        screen += ROW_SIZE;
        pos += ROW_SIZE;
    }
    else
    {
        memcpy((void*) MEM_BASE, (void*) (screen + MEM_BASE), SCR_SIZE);
        pos -= screen;
        screen = 0;
    }
    set_screen();
}

static void command_lf()
{
    int y = pos / ROW_SIZE;
    if (y + 1 < HEIGHT)
    {
        pos += ROW_SIZE;
        return;
    }
    scroll_up();
}

static void command_cr()
{
    uint x = pos % ROW_SIZE;
    pos -= x;
}

static void command_bs()
{
    uint x = pos % ROW_SIZE;
    x >>= 1;
    if (x > 0)
    {
        pos -= 2;
        *(u16 *) (pos + MEM_BASE) = 0x0720;
    }
}

static void command_del()
{
    *(u16*) (pos + MEM_BASE) = 0x0720;
}

void console_write(char* buf, u32 count)
{
    char ch;
    char* ptr = (char*) (pos + MEM_BASE);
    while (count--)
    {
        ch = *buf++;
        switch (ch)
        {
        case ASCII_NUL:
            break;
        case ASCII_BEL:
            break;
        case ASCII_BS:
            command_bs();
            break;
        case ASCII_HT:
            break;
        case ASCII_LF:
            command_lf();
            command_cr();
            break;
        case ASCII_VT:
            break;
        case ASCII_FF:
            command_lf();
            break;
        case ASCII_CR:
            command_cr();
            break;
        case ASCII_DEL:
            command_del();
            break;
        default:
            // 这个iftrue完全是没必要的，但是不加在低版本的gcc中编译不过去，加这句完全是为了兼容低版本gcc
            if (true) 
            {
                uint x = pos % ROW_SIZE;
                /*
                记录一下这里踩的一个坑，这里我的意图是x除以2，但我不小心写成了左移，这样就导致x的值被放大了，本来应该80个字符的时候换行，结果在40个字符就进入了换行逻辑。
                当然影响还不止这么一点点。

                当我们算错x的值的时候，pos的值还很小，比如40，然后错误进入换行逻辑时，去自减了一个ROW_SIZE，这个问题可就大了
                因为这样把x减成一个负数，然后后面的逻辑就全变了，然后整个屏幕都被清掉了。
                */
                x >>= 1;
                if (x >= WIDTH)
                {
                    pos -= ROW_SIZE;
                    command_lf();
                }
                
                *ptr = ch;
                ptr++;
                *ptr = 0x07;
                ptr++;
                pos += 2;
                break;
            }
        }
    }
    set_cursor();
}

void console_write_fixed_position(char* buf, u32 count, int offset)
{
    char ch;
    char *ptr = (char*) MEM_BASE + offset;
    while (count--)
    {
        ch = *buf++;
        switch (ch)
        {
        case ASCII_NUL:
            break;
        case ASCII_BEL:
            break;
        case ASCII_BS:
            command_bs();
            break;
        case ASCII_HT:
            break;
        case ASCII_LF:
            command_lf();
            command_cr();
            break;
        case ASCII_VT:
            break;
        case ASCII_FF:
            command_lf();
            break;
        case ASCII_CR:
            command_cr();
            break;
        case ASCII_DEL:
            command_del();
            break;
        default:
            if (true)
            {
                *ptr = ch;
                ptr++;
                *ptr = 0x07;
                ptr++;
            }
            
        }
    }
    set_cursor();
}

void console_init(void)
{
    console_clear();
}